package org.jtsmix.mixfile;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MixFileImpl implements MixFile {

    private static final Logger logger = Logger.getLogger("jtsmix.mixfile");
    private MixFileHeader mixHeader;
    private MixDB mixDB = null;
    private File mixfile;
    private RandomAccessFile fh = null;
    private GameType gameType = null;

    /**
     * get list of files
     * <p/>
     * File: opened mix filename filename or #crc | offset | size
     * -------------------------------------- total files count, body size
     *
     * @return list of files in printable format
     */
    @Override
    public String toString() {
        throw new UnsupportedOperationException("Not implemented yet.");
    }

    /**
     * get local filename database
     * <p/>
     * Common filenames are stored in globalMixDatabase.dat. However custom
     * filenames may be unknown. That's why their filenames are usually stored
     * in local mix database.dat inside mix file. Database format is same as in
     * XCC tools by Olaf van der Speck to keep compatibility.
     */
    private void loadLocalMixDB() {
        throw new UnsupportedOperationException("Not implemented yet.");
    }

    @Override
    public void open(String filePath, GameType gameType) throws FileNotFoundException, IOException {
        this.gameType = gameType;

        mixfile = new File(filePath);
        FileInputStream fs = new FileInputStream(mixfile);
        mixHeader = new MixFileHeader(fs);

        fs.close();

        // TODO loadLocalMixDB();

        fh = new RandomAccessFile(mixfile, "rw");
    }

    @Override
    public void close() throws IOException {
        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        fh.close();
        fh = null;
    }


    /**
     * @return
     * @throws IOException
     * @deprecated @param fileName
     */
    @Override
    public byte[] read(String fileName) throws IOException {


        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        byte buffer[];
        MixFileIndexEntry foundFile;

        if (!contains(fileName)) {
            throw new FileNotFoundException("File " + fileName + " not in mix archive.");
        }

        foundFile = mixHeader.getFileIndexEntry(fileName, gameType);
        buffer = new byte[(int) foundFile.getSize()];

        fh.seek(mixHeader.getDataOffset() + foundFile.getOffset());
        fh.read(buffer);
        return buffer;
    }

    @Override
    public boolean contains(String fileName) {

        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        return mixHeader.contains(fileName, gameType);
    }

    public boolean contains(long crcID) {

        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        return mixHeader.contains(crcID);
    }

    @Override
    public void write(String fileName, byte[] data) throws IOException {

        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        if (contains(fileName)) {
            remove(fileName);
        }
        FileOutputStream ofs = null;

        try {
            ofs = new FileOutputStream(mixfile.getAbsolutePath() + ".mod");
        } catch (FileNotFoundException ex) {
            Logger.getLogger(MixFileImpl.class.getName()).log(Level.SEVERE, null, ex);
        }

        if (ofs == null) {
            return;
        }

        fh.seek(mixHeader.getDataOffset());

        MixFileHeader newheader = mixHeader;

        newheader.addFile(CRCCounter.getWestwoodCRC(fileName, gameType), data.length);
        newheader.writeHeader(ofs);


        // copy mix body

        byte[] mixbody = new byte[4096];
        int i;
        while ((i = fh.read(mixbody)) != -1) {
            ofs.write(mixbody, 0, i);
        }
        ofs.write(data);
        ofs.close();

    }

    private void removefromto(long from, long to, OutputStream ofs) throws IOException {


        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        if (ofs == null) {
            return;
        }

        fh.seek(mixHeader.getDataOffset() + 12);
        byte data[] = new byte[(int) from];
        fh.read(data);
        ofs.write(data);

        fh.seek(to);

        byte[] mixbody = new byte[4096];
        int i;
        while ((i = fh.read(mixbody)) != -1) {
            ofs.write(mixbody, 0, i);
        }

        ofs.close();

    }

    @Override
    public List<MixFileIndexEntry> getFileList() {

        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        return mixHeader.getFileList();
    }

    @Override
    public List<String> getFileNameList() {


        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        List<String> ret = new ArrayList<String>();
        String strToAdd;
        for (MixFileIndexEntry file : mixHeader.getFileList()) {
            if (mixDB != null) {
                strToAdd = mixDB.getName(file.getCrcId());
            } else {
                strToAdd = MixDB.getGlobalName(file.getCrcId(), this.gameType);
            }
            if (strToAdd == null) {
                ret.add("# " + file.getCrcId());
            } else {
                ret.add(strToAdd);
            }
        }
        return ret;
    }

    @Override
    public InputStream getFileStream(String fileName) throws IOException, FileNotFoundException {


        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        MixFileIndexEntry foundFile;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        if (!contains(fileName)) {
            throw new FileNotFoundException("File " + fileName + " not found in mix archive.");
        }

        foundFile = mixHeader.getFileIndexEntry(fileName, gameType);

        fh.seek(mixHeader.getDataOffset() + foundFile.getOffset());
        byte buffer[] = new byte[4096];
        while (fh.read(buffer) != -1) {
            bos.write(buffer);
        }
        return new ByteArrayInputStream(bos.toByteArray());
    }

    @Override
    public void writeFileToStream(String fileName, OutputStream os) throws IOException {


        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        MixFileIndexEntry foundFile;

        if (!contains(fileName)) {
            throw new FileNotFoundException("File " + fileName + " not found in mix archive.");
        }

        foundFile = mixHeader.getFileIndexEntry(fileName, gameType);

        fh.seek(mixHeader.getDataOffset() + foundFile.getOffset());
        logger.log(Level.INFO, "Extracting file at address: {0}", (mixHeader.getDataOffset() + foundFile.getOffset()));

        byte buffer[] = new byte[4096];
        int i;
        long fsize = foundFile.getSize();
        long rsize = 0;
        while ((i = fh.read(buffer)) != -1) {
            if (i > fsize) {
                os.write(buffer, 0, (int) fsize);
                rsize += fsize;
            } else {
                os.write(buffer, 0, i);
                fsize -= i;
                rsize += i;
            }

        }
        logger.log(Level.INFO, "Extracted {0} bytes.", rsize);

    }

    @Override
    public boolean remove(String fileName) throws IOException {


        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        long crc = CRCCounter.getWestwoodCRC(fileName, gameType);
        for (MixFileIndexEntry file : mixHeader.getFileList()) {
            if (crc == file.getCrcId()) {
                FileOutputStream ofs = null;
                try {
                    ofs = new FileOutputStream(mixfile.getAbsolutePath() + ".mod");
                } catch (FileNotFoundException ex) {
                    Logger.getLogger(MixFileImpl.class.getName()).log(Level.SEVERE, "Currently opened mix archive not found. Did you delete it?", ex);
                    return false;
                }
                mixHeader.removeFile(crc);
                mixHeader.writeHeader(ofs);
                removefromto(file.getOffset(), mixHeader.getDataOffset() + 12 + file.getOffset() + file.getSize(), ofs);
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean read(String fileName, OutputStream os) throws IOException {


        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        byte buffer[];
        MixFileIndexEntry foundFile;

        if (!contains(fileName)) {
            return false;
        }

        foundFile = mixHeader.getFileIndexEntry(fileName, gameType);
        buffer = new byte[4096];

        fh.seek(mixHeader.getDataOffset() + foundFile.getOffset());
        int i;
        long rsize = 0;
        while ((i = fh.read(buffer)) != -1) {
            os.write(buffer, 0, i);
            rsize += i;
        }
        return true;
    }

    @Override
    public MixInfo getMixInfo() {


        if (!isOpened()) {
            throw new IllegalStateException("No mix archive opened.");
        }

        return new MixInfo(mixfile.getName(), mixHeader.getNumFiles(), mixHeader.getSize());
    }

    @Override
    public boolean isOpened() {
        return (fh != null);
    }
    /**
     * @Override public void write(String fileName, OutputStream data) throws
     * IOException { throw new UnsupportedOperationException("Not supported
     * yet."); }
     *
     */
}